/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include "glheaders.h"

#include <assert.h>
#include <string.h>
#include <math.h>
#include "renderUtils.h"
#include "mouseUtils.h"
#include "graph.h"
#include "viewcreators.h"
#include "ui/uiUtils.h"
#include "controller.h"
#include "PropertyUtils.h"

/*****************************
PARALLEL COORDINATES
*****************************/
class ParallelCoords: public View {
    float *vertices;
    GLuint *indices;
    vector<string> cachedProperties;
    int hoveredNodeIndex;
	Node *hoveredNode;

public:
    ParallelCoords(const char *title) : View("Parallel Coordinates", title) {
        setPropertyStringList("Data.Color", Controller::addVirtualPrefix("node.color"), "node");
        setPropertyStringList("Data.Opacity", "Default", "node");
        setPropertyStringList("Data.Colormap", Controller::addVirtualPrefix("node.colormap"), "colormap");
        setPropertyStringList("Data.Properties", "Default", "node", true);
				setPropertyInt("Lines.Width", 2);
				setPropertyFloat("Lines.Transparency", 0.5);

        vertices = 0;
        indices = 0;
        setMargins(120,120,40,80);
        hoveredNodeIndex = -1;
    }

    ~ParallelCoords() {
        if(vertices) delete vertices;
        if(indices) delete indices;
    }


    void createGeometry(int numProperties, int numNodes) {
        if(vertices) delete vertices;
        if(indices) delete indices;

        vertices = new float[numProperties*numNodes*2];
        indices = new GLuint[numProperties*numNodes];
    }
	
	void updateSelection(int marker) {
		int numProperties = cachedProperties.size();
		int m = 0;
		vector<PropertyBag*> selection;
		vector<PropertyBag*> unselection;
		NodeIterator *it = graph->createNodeIterator();
		for(it->begin();it->hasMoreElements();) {
			Node *node = (Node*) it->next();
			int sel =  node->getPropertyInt("node.selected");
			if(sel==selectionMarker) unselection.push_back(node);
			
			for(int i = 0; i<numProperties-1; i++) {
				float x1 = vertices[m+0];
				float y1 = vertices[m+1];
				float x2 = vertices[m+2];
				float y2 = vertices[m+3];
				
				if(intersectsSelection(x1,y1,x2,y2)) {
					selection.push_back(node);
				}
				m+=2;
			}
			m+=2;	
		}
		graph->destroyIterator(it);
		selectElements(unselection, 0);
		selectElements(selection, marker);
	}

    virtual void drag(float ppx, float ppy, float dx, float dy, int modifiers) {
        View::drag(ppx,ppy,dx,dy,modifiers);

        if(dragMode==DRAG && validGeometryCache) {
					updateSelection(selectionMarker);
        }
    }
	
    void getGeometry() {
			vector<string> selectedProperties = PropertyUtils::parseMultiProperty(getPropertyString("Data.Properties"));
        int numProperties = selectedProperties.size();
        createGeometry(numProperties, graph->numNodes());

        float delta = 1.0/(float) (numProperties-1);
        int m = 0;
				NodeIterator *it = graph->createNodeIterator();
				for(it->begin();it->hasMoreElements();) {
					Node *node = (Node*) it->next();
								for(int i = 0; i<numProperties; i++) {
						float x1 = delta* (float) i;
						string p1 = PROPERTY(selectedProperties[i]);

						float y1 = graph->getAsFloat(node, p1, 1.0, true);

						vertices[2*m+0] = x1;
						vertices[2*m+1] = y1;

						indices[m+0] = m;

						m++;
            }
        }
				graph->destroyIterator(it);
				validGeometryCache = true;
    }
	

    void render() {
        View::render();

			/***********************************************/
			/**  Parse properties. Validate Geom.Cache    **/
			/***********************************************/

			vector<string> selectedProperties = PropertyUtils::parseMultiProperty(getPropertyString("Data.Properties"));
        int numProperties = (int) selectedProperties.size();//getPropertyInt("NumDimensions");

        if(numProperties!=(int) cachedProperties.size()) {
            invalidateGeometryCache();
        }
        for(int i=0;i<min((int) cachedProperties.size(),numProperties);i++) {
            string prop = selectedProperties[i];
            if(prop!=cachedProperties[i]) {
                invalidateGeometryCache();
                break;
            }
        }

        if(!validGeometryCache) getGeometry();
        cachedProperties.clear();

				
			/****************/
			/* Axes         */
			/****************/
				float delta = 1.0/(float) (numProperties-1);

        for(int i=0;i<numProperties;i++) {
            string prop = PROPERTY(selectedProperties[i]);
            cachedProperties.push_back(prop);

            //glColor4f(0,0,0,0.9);
            glColor4f(fColor_front,fColor_front,fColor_front,0.9);
            float x = delta * (float) i;
            glBegin(GL_LINES);
            glVertex2f(x, 0);
            glVertex2f(x, 1);
            glEnd();

            //glColor4f(0,0,0,0.9);
            glColor4f(fColor_front,fColor_front,fColor_front,0.9);
            float minv, maxv;
            graph->getStatManager()->getMinMaxFloat(prop, minv, maxv);
            char strMin[30], strMax[30];
            sprintf(strMin, "%.2f", minv);
            sprintf(strMax, "%.2f", maxv);

            renderText(x - xpixels(5), 0, strMin, "right", "top");
            renderText(x - xpixels(5), 1, strMax, "right", "bottom");

 						setFont(getCurrentFont(), getCurrentFontSize(), "bold");
            renderText(x, -ypixels(20), (char*) prop.c_str(), "center", "middle");
						setFont(getCurrentFont(), getCurrentFontSize(), "normal");
        }
 
 			/****************/
			/* Data Lines   */
			/****************/

				int lineWidth = getPropertyInt("Lines.Width");
        float lineTransparency = getPropertyFloat("Lines.Transparency");
        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(2, GL_FLOAT, 0, vertices);
				glDisableClientState(GL_COLOR_ARRAY);

        const GLuint *t = indices;
				float rgb[3];
				string cmap = PROPERTY(getPropertyString("Data.Colormap"));
				string colorProp = PROPERTY(getPropertyString("Data.Color"));
				string opacityProp = PROPERTY(getPropertyString("Data.Opacity"));  
				NodeIterator *it = graph->createNodeIterator();
				int k = 0;
						for(it->begin();it->hasMoreElements();) {
								Node *node = (Node*) it->next();
								rgb[0] = rgb[1] = rgb[2] = 0.0;
								getAsColor(node, colorProp, cmap, rgb);
								float transparency = graph->getAsFloat(node,opacityProp);
								int selected = node->getPropertyInt("node.selected");
								if(selected>0) {
										getSelectionColor(selected, rgb);
								}

								glLineWidth(1);
								//glColor4f(0,0,0, lineTransparency*transparency);
								glColor4f(fColor_front,fColor_front,fColor_front, lineTransparency*transparency);
								glDrawElements(GL_LINE_STRIP, numProperties, GL_UNSIGNED_INT, &(indices[numProperties*k]));


								glLineWidth(lineWidth);
								glColor4f(rgb[0], rgb[1], rgb[2], lineTransparency*transparency);
								glDrawElements(GL_LINE_STRIP, numProperties, GL_UNSIGNED_INT, &(indices[numProperties*k]));
								t = t + numProperties;
					k++;
				}
				graph->destroyIterator(it);
			
			
 			/***********************/
			/* Draw Hovered Node   */
			/***********************/
			if(hoveredNodeIndex>=0) {
            float rgb[3];
            getSelectionColor(1, rgb);
            glLineWidth(lineWidth+1);
            glColor4f(rgb[0], rgb[1], rgb[2], 0.95);
            glDrawElements(GL_LINE_STRIP, numProperties, GL_UNSIGNED_INT, &(indices[numProperties*hoveredNodeIndex]));

            for(int i=0;i<numProperties;i++) {
                string prop = PROPERTY(selectedProperties[i]);

                //glColor4f(0,0,0,0.9);
                glColor4f(fColor_front,fColor_front,fColor_front,0.9);

                //glColor4f(0,0,0,0.9);
                float value = graph->getAsFloat(hoveredNode,prop, 0.0, false);
                float y = graph->getAsFloat(hoveredNode,prop, 0.0, true);
                float x = delta * (float) i;
                char strVal[30];
                string str = graph->getAsString(hoveredNode,prop);
                sprintf(strVal, "%.2f", value);
                glLineWidth(1);
                //glColor4f(1,1,1,1.0);
                glColor4f(fColor_back,fColor_back,fColor_back,1.0);

                renderRoundTextBox(x, y, (char*) str.c_str(), "center", "middle", xpixels(5.0), ypixels(5.0));
            }

        }

        glLineWidth(1);
        glDisableClientState(GL_VERTEX_ARRAY);
    }
	
	void updateHoveredNode(float rx, float ry) {
		int numProperties = cachedProperties.size();
		int m = 0;
		hoveredNodeIndex = -1;
		NodeIterator *it = graph->createNodeIterator();
		int k = 0;
		for(it->begin();it->hasMoreElements();) {
			Node *node = (Node*) it->next();
			
			for(int i = 0; i<numProperties-1; i++) {
				float x1 = vertices[m+0];
				float y1 = vertices[m+1];
				float x2 = vertices[m+2];
				float y2 = vertices[m+3];
				
				if(intersectLineBox(x1,y1,x2,y2,rx-xpixels(2),ry-ypixels(2),xpixels(4), ypixels(4))) {
					hoveredNodeIndex = k;
					hoveredNode = node;
					//break;
				}
				m+=2;
			}
			m+=2;
			k++;	
		}
		graph->destroyIterator(it);
	}

    void mouseMove(float px, float py, float dx, float dy, int modifiers) {
        View::mouseMove(px, py, dx, dy, modifiers);
        float rx, ry;
        toView(px, py, rx, ry);
        if(validGeometryCache) {
					updateHoveredNode(rx, ry);
				}
    }
};




View *createParallel(string name) {
    ParallelCoords *view = new ParallelCoords(name.c_str());
    view->setLocation(-1.0, 1.0);
    view->setSize(1.0, 0.7);
    view->setVisible(true);
    view->addSelectionWidget();
    view->addCloseButton();
    view->addTransparentButton();
    return view;
}
